/*
 * @auther  yaoleilei<windowhero@sina.com>
 * @date    Fri Sep 22 20:45:49 CST 2017
 */
#ifndef __URLMATCH__LIST_H__
#define __URLMATCH__LIST_H__
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <pthread.h>
#include <time.h>
#include "list.h"

#if 1
#define URLMATCH_LOG(fmt, ...)     printf("line %u: "fmt, __LINE__, ##__VA_ARGS__)
#define URLMATCH_ERR(fmt, ...)     printf("line %u: "fmt, __LINE__, ##__VA_ARGS__)
#else
#define URLMATCH_LOG(...)
#define URLMATCH_ERR(fmt, ...)     printf("line %u: "fmt, __LINE__, ##__VA_ARGS__)
#endif

typedef struct urlmatch_pattern {
    struct urlmatch_pattern *next;
    void                    *userdata;
} urlmatch_pattern_t;

typedef struct cpn {
    uint8_t c;
    void *p;
    struct cpn *n;
} cpn_t;

typedef struct urlmatch_state{
#ifdef ENABLE_URLMATCH_NODELIST
    struct list_head node;
#endif
    cpn_t *cpn;
    urlmatch_pattern_t *urlmatch_pattern;
} urlmatch_state_t;

typedef void (*URLMATCH_USERFREE)(void *);

struct urlmatch {
    unsigned item_count;
    unsigned pattern_count; uint64_t pattern_mem;
    unsigned node_count;    uint64_t node_mem;
    unsigned cpn_count;     uint64_t cpn_mem;
    struct list_head state_list;
    urlmatch_state_t        root_state;
    URLMATCH_USERFREE       urlmatch_userfree;
};

static inline urlmatch_pattern_t *urlmatch_find_long_host(struct urlmatch *m, const char *host, int host_len)
{
    urlmatch_state_t *urlmatch_state = &m->root_state;
    urlmatch_pattern_t *urlmatch_pattern;
    urlmatch_pattern_t *urlmatch_pattern_last = NULL;
    int i;
    //printf("test host %.*s len %d\n", host_len, host, host_len);
    for (i = host_len-1; i >= 0; i--){
        //printf("test %c\n", host[i]);
        unsigned char next_state_pos = (unsigned char)host[i];
        if (urlmatch_state->cpn == NULL) break;
        cpn_t *tmp;
        for (tmp = urlmatch_state->cpn; tmp; tmp = tmp->n) {
            if (tmp->c == next_state_pos) break;
        }
        if (tmp == NULL) break;
        urlmatch_state = tmp->p;
        if (urlmatch_state == NULL) break;
        for(urlmatch_pattern = urlmatch_state->urlmatch_pattern; urlmatch_pattern; urlmatch_pattern = urlmatch_pattern->next) {
            urlmatch_pattern_last = urlmatch_pattern;
        }
    }
    return urlmatch_pattern_last;
}

static inline urlmatch_pattern_t *urlmatch_find(struct urlmatch *m, const char *host, int host_len, const char *path, int path_len,
        int match(struct urlmatch *m, urlmatch_pattern_t *urlmatch_pattern, const char *host, int host_len, const char *path, int path_len, int match_host_pos, int match_path_pos, void *context),
        void *context)
{
    urlmatch_state_t *urlmatch_state = &m->root_state;
    urlmatch_pattern_t *urlmatch_pattern;
    int i;
    for (i = host_len-1; i >= 0; i--){
        unsigned char next_state_pos = host[i]; if (urlmatch_state->cpn == NULL) return NULL;
        cpn_t *tmp;
        for (tmp = urlmatch_state->cpn; tmp; tmp = tmp->n) {
            if (tmp->c == next_state_pos) break;
        }
        if (tmp == NULL) return NULL;
        urlmatch_state = tmp->p;
        if (urlmatch_state == NULL) return NULL;
        for(urlmatch_pattern = urlmatch_state->urlmatch_pattern; urlmatch_pattern; urlmatch_pattern = urlmatch_pattern->next) {
            if (match == NULL) return urlmatch_pattern;
            if (match(m, urlmatch_pattern, host, host_len, path, path_len, i, -1, context) > 0) return urlmatch_pattern;
        }
    }
    for (i = 0; i < path_len; i++){
        unsigned char next_state_pos = path[i]; if (urlmatch_state->cpn == NULL) return NULL;
        cpn_t *tmp;
        for (tmp = urlmatch_state->cpn; tmp; tmp = tmp->n) {
            if (tmp->c == next_state_pos) break;
        }
        if (tmp == NULL) return NULL;
        urlmatch_state = tmp->p;
        if (urlmatch_state == NULL) return NULL;
        for(urlmatch_pattern = urlmatch_state->urlmatch_pattern; urlmatch_pattern; urlmatch_pattern = urlmatch_pattern->next) {
            if (match == NULL) return urlmatch_pattern;
            if (match(m, urlmatch_pattern, host, host_len, path, path_len, 0, i, context) > 0) return urlmatch_pattern;
        }
    }
    return NULL;
}

static inline int urlmatch_add_url_extra(struct urlmatch *m, const char *host, int host_len, const char *path, int path_len, void *userdata, int update)
{
    int i;

    if (update) {
        urlmatch_pattern_t *t = urlmatch_find(m, host, host_len, path, path_len, NULL, NULL);
        if (t) {
            if (t->userdata) { if (m->urlmatch_userfree) m->urlmatch_userfree(t->userdata); else free(t->userdata); }
            t->userdata = userdata;
            return 0;
        }
    }
    urlmatch_pattern_t *urlmatch_pattern = calloc(1, sizeof(urlmatch_pattern_t)); if (urlmatch_pattern == NULL) { URLMATCH_ERR("fail to calloc\n"); return -1; }
    m->pattern_mem += sizeof(urlmatch_pattern_t);
    m->pattern_count++;
    urlmatch_pattern->userdata = userdata;

    urlmatch_state_t *urlmatch_state = &m->root_state;

    for (i = host_len - 1; i >= 0; i--) {
        unsigned char next_state_pos = (unsigned char)host[i];
        cpn_t *tmp;
        for (tmp = urlmatch_state->cpn; tmp; tmp = tmp->n) {
            if (tmp->c == next_state_pos) break;
        }
        if (tmp == NULL) {
            tmp = calloc(1, sizeof(*tmp)); if (tmp == NULL) { URLMATCH_ERR("error\n"); exit(1); } m->cpn_mem += sizeof(cpn_t); m->cpn_count++;
            tmp->p = calloc(1, sizeof(urlmatch_state_t)); if (tmp->p == NULL) { URLMATCH_ERR("fail to calloc[%m]\n"); return -1; } m->node_mem += sizeof(urlmatch_state_t); m->node_count++;
            tmp->c = next_state_pos;
            tmp->n = urlmatch_state->cpn;
            urlmatch_state->cpn = tmp;
        }
        urlmatch_state = tmp->p;
    }

    for (i = 0; i < path_len; i++) {
        unsigned char next_state_pos = (unsigned char)path[i];
        cpn_t *tmp;
        for (tmp = urlmatch_state->cpn; tmp; tmp = tmp->n) {
            if (tmp->c == next_state_pos) break;
        }
        if (tmp == NULL) {
            tmp = calloc(1, sizeof(*tmp)); if (tmp == NULL) { URLMATCH_ERR("error\n"); exit(1); } m->cpn_mem += sizeof(cpn_t); m->cpn_count++;
            tmp->p = calloc(1, sizeof(urlmatch_state_t)); if (tmp->p == NULL) { URLMATCH_ERR("fail to calloc[%m]\n"); return -1; }
            tmp->c = next_state_pos;
            tmp->n = urlmatch_state->cpn;
            urlmatch_state->cpn = tmp;
            m->node_mem += sizeof(urlmatch_state_t);
            m->node_count++;
        }
        urlmatch_state = tmp->p;
    }
    urlmatch_pattern->next = urlmatch_state->urlmatch_pattern;
    urlmatch_state->urlmatch_pattern = urlmatch_pattern;
    m->item_count++;
    return 0;
}
static inline int urlmatch_add_url(struct urlmatch *m, const char *url, void *userdata)
{
    if (url == NULL) return -1;
    int url_len = strlen(url);
    const char *host = url;
    const char *path = memchr(url, '/', url_len); if (path == NULL) path = url + url_len;
    int host_len = path - host;
    int path_len = url_len - host_len;

    return urlmatch_add_url_extra(m, host, host_len, path, path_len, userdata, 1);
}

static inline struct urlmatch *urlmatch_new(URLMATCH_USERFREE urlmatch_userfree)
{
    struct urlmatch *m = calloc(1, sizeof(struct urlmatch)); if (m == NULL) { URLMATCH_ERR("fail to calloc[%m]\n"); return NULL; }
    m->urlmatch_userfree = urlmatch_userfree;
    INIT_LIST_HEAD(&m->state_list);
    return m;
}

#ifdef ENABLE_URLMATCH_NODELIST
static inline void urlmatch_free(struct urlmatch *m)
{
    if (m == NULL) return;

    urlmatch_state_t *node, *n;
    list_for_each_entry_safe(node, n, &m->state_list, node) {
        list_del(&node->node);

        urlmatch_pattern_t *urlmatch_pattern = node->urlmatch_pattern;
        while (urlmatch_pattern) {
            urlmatch_pattern_t *urlmatch_pattern_next = urlmatch_pattern->next;
            if (urlmatch_pattern->userdata) {
                if (m->urlmatch_userfree) m->urlmatch_userfree(urlmatch_pattern->userdata); else free(urlmatch_pattern->userdata);
            }
            free(urlmatch_pattern);
            urlmatch_pattern = urlmatch_pattern_next;
        }
        free(node);
    }
    free(m);
}
#endif

static inline void urlmatch_stat(struct urlmatch *m)
{
    LLOG("item_count %u\n", m->item_count);
    LLOG("pattern     size %3lu count %-10u mem %5"PRIu64"m %"PRIu64"\n", sizeof(urlmatch_pattern_t)       , m->pattern_count   , m->pattern_mem   /1000000, m->pattern_mem   );
    LLOG("node        size %3lu count %-10u mem %5"PRIu64"m %"PRIu64"\n", sizeof(urlmatch_state_t)         , m->node_count      , m->node_mem      /1000000, m->node_mem     );
    LLOG("cpn         size %3lu count %-10u mem %5"PRIu64"m %"PRIu64"\n", sizeof(cpn_t)                    , m->cpn_count       , m->cpn_mem       /1000000, m->cpn_mem       );
}

#endif
